using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Globalization;
using System.Threading;
using System.Diagnostics;
using UnityEngine;

namespace VRCPRServer.NetWork
{
    public class AsyncTcpClient : IDisposable
    {
        #region  Fields
        private Socket AsyncClient;
        private IAsyncResult connectResult;
        private static readonly object lockObj = new object();
        #endregion

        #region Properted
        /// <summary>
        /// 是否已与服务器建立连接
        /// </summary>
        public bool IsConnected { get; private set; }

        /// <summary>
        /// 远端服务器的IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }

        /// <summary>
        /// 远端服务器端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// 连接重试次数
        /// </summary>
        public int Retries { get; set; }

        /// <summary>
        /// 连接重试间隔(秒)
        /// </summary>
        public int RetryInterval { get; set; }

        /// <summary>
        /// 远端服务器端点
        /// </summary>
        public IPEndPoint RemoteIPEndPoint { get { return new IPEndPoint(Addresses[0], Port); } }

        /// <summary>
        /// 本地客户端终结点
        /// </summary>
        protected IPEndPoint LocalIPEndPoint { get; private set; }

        /// <summary>
        /// 通信所使用的编码
        /// </summary>
        /// </summary>
        public Encoding Encoding { get; set; }

        /// <summary>
        /// 接收数组的长度
        /// </summary>
        public int BufferLength { get; set; } = 1024 * 1024;
        #endregion

        #region Ctors
        public AsyncTcpClient(IPEndPoint remoteEP)
            : this(new[] { remoteEP.Address }, remoteEP.Port)
        {

        }

        public AsyncTcpClient(IPEndPoint remoteEP, IPEndPoint localEP)
            : this(new[] { remoteEP.Address }, remoteEP.Port, localEP)
        {

        }

        public AsyncTcpClient(IPAddress remoteIPaddress, int remotePort)
            : this(new[] { remoteIPaddress }, remotePort)
        {

        }

        public AsyncTcpClient(IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP)
            : this(new[] { remoteIPAddress }, remotePort, localEP)
        {

        }

        public AsyncTcpClient(string remoteHostName, int remotePort)
            : this(Dns.GetHostAddresses(remoteHostName), remotePort)
        {

        }

        public AsyncTcpClient(string remoteHostNmae, int remotePort, IPEndPoint localEP)
            : this(Dns.GetHostAddresses(remoteHostNmae), remotePort, localEP)
        {

        }

        public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort)
            : this(remoteIPAddresses, remotePort, null)
        {
        }

        public AsyncTcpClient(IPAddress[] remoteIPAddresses, int remotePort, IPEndPoint localEP)
        {
            this.Addresses = remoteIPAddresses;
            this.Port = remotePort;
            this.LocalIPEndPoint = localEP;
            this.Encoding = Encoding.Default;
            try
            {
                this.AsyncClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            }
            catch (Exception e)
            {
                Console.WriteLine("TCPClient[AsyncTcpClient]", e.Message);
            }

            Retries = 3;
            RetryInterval = 5;
        }

        #endregion

        #region Connect
        /// <summary>
        /// 连接服务器
        /// </summary>
        /// <returns></returns>
        public AsyncTcpClient Connect()
        {
            if (!IsConnected)
            {
                try
                {
                    connectResult = AsyncClient.BeginConnect(Addresses, Port, HandleTcpServerConnected, AsyncClient);
                }
                catch (Exception e)
                {
                    RaiseServerExceptionOccurred(Addresses, Port, e);
                }
            }
            return this;
        }

        /// <summary>
        /// 尝试重连
        /// </summary>
        /// <returns></returns>
        public void ReConnect()
        {
            lock (lockObj)
            {
                if ((!IsConnected && AsyncClient != null) && connectResult.IsCompleted)
                {
                    try
                    {
                        this.AsyncClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                        connectResult = AsyncClient.BeginConnect(Addresses, Port, HandleTcpServerConnected, AsyncClient);
                        connectResult.AsyncWaitHandle.WaitOne();
                        Retries++;
                    }
                    catch (SocketException e)
                    {
                        RaiseServerExceptionOccurred(Addresses, Port, e);
                    }
                }
            }

        }

        public AsyncTcpClient Close()
        {
            try
            {
                Retries = 0;
                IsConnected = false;
                AsyncClient.Shutdown(SocketShutdown.Both);
                AsyncClient.Close();
            }
            catch (Exception e)
            {
                MonoBehaviour.print($"AsyncTcpClient-Close {e}");
            }
            RaiseServerDisConnected(Addresses, Port);
            return this;
        }

        #endregion

        #region Receive
        private void HandleTcpServerConnected(IAsyncResult ar)
        {
            try
            {
                Socket clent = (Socket)ar.AsyncState;
                clent.EndConnect(ar);
                RaiseServerConnected(Addresses, Port);

                TcpServerState tcpServerState = new TcpServerState(AsyncClient, BufferLength);
                AsyncClient.BeginReceive(tcpServerState.Buffer, 0, tcpServerState.Buffer.Length, SocketFlags.None, HandleDatagramReceived, tcpServerState);
                Retries = 0;
            }
            catch (Exception ex)
            {
                Retries++;
                RaiseServerExceptionOccurred(Addresses, Port, ex);
                return;
            }
        }

        private void HandleDatagramReceived(IAsyncResult ar)
        {
            if (AsyncClient == null) return;
            TcpServerState serverState = (TcpServerState)ar.AsyncState;
            Socket clent = serverState.TcpClient;
            byte[] receivedBytes = null;
            int numberOfReadBytes = 0;
            try
            {
                numberOfReadBytes = clent.EndReceive(ar);
                receivedBytes = new byte[numberOfReadBytes];
                Buffer.BlockCopy(serverState.Buffer, 0, receivedBytes, 0, numberOfReadBytes);
                RaiseDatagramReceive(AsyncClient, receivedBytes);
                RaisePlaintextReceived(AsyncClient, receivedBytes);
            }
            catch (Exception e)
            {
                RaiseServerExceptionOccurred(Addresses, Port, e);
                return;
            }
            if (numberOfReadBytes == 0)
            {
                RaiseServerExceptionOccurred(Addresses, Port, new Exception("AsyncTcpClient-HandleDatagramReceived- Receive Count is zero"));
                return;
            }


            clent.BeginReceive(serverState.Buffer, 0, serverState.Buffer.Length, SocketFlags.None, HandleDatagramReceived, serverState);
        }

        #endregion

        #region Event
        /// <summary>
        /// 接收数据报文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceive;

        /// <summary>
        /// 接收数据报文明文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceive;

        private void RaiseDatagramReceive(Socket sender, byte[] datagram)
        {
            DatagramReceive?.Invoke(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
        }

        private void RaisePlaintextReceived(Socket sender, byte[] datagram)
        {
            PlaintextReceive?.Invoke(this, new TcpDatagramReceivedEventArgs<string>(sender, this.Encoding.GetString(datagram)));
        }

        /// <summary>
        /// 与服务器的连接已建立事件
        /// </summary>
        public event EventHandler<TcpServerConnectedEventArgs> ServerConnected;

        /// <summary>
        /// 与服务器的连接已断开事件
        /// </summary>
        public event EventHandler<TcpServerDisconnectedEventArgs> ServerDisConnected;

        /// <summary>
        /// 与服务器的连接发生异常事件
        /// </summary>
        public event EventHandler<TcpServerExceptionOccurredEventArgs> ServerExceptionOccurred;

        private void RaiseServerConnected(IPAddress[] iPAddresses, int port)
        {
            IsConnected = true;
            ServerConnected?.Invoke(this, new TcpServerConnectedEventArgs(iPAddresses, port));
        }

        private void RaiseServerDisConnected(IPAddress[] iPAddresses, int port)
        {
            IsConnected = false;
            ServerDisConnected?.Invoke(this, new TcpServerDisconnectedEventArgs(iPAddresses, port));
        }

        private void RaiseServerExceptionOccurred(IPAddress[] iPAddresses, int port, Exception exception)
        {
            IsConnected = false;
            ServerExceptionOccurred?.Invoke(this, new TcpServerExceptionOccurredEventArgs(iPAddresses, port, exception));
        }
        #endregion

        //增加一个主动和服务器断开连接的方法
        #region Send
        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="datagram">报文</param>
        public void Send(byte[] datagram)
        {
            if (datagram == null)
                MonoBehaviour.print("datagram is null");

            if (!IsConnected)
            {
                RaiseServerExceptionOccurred(Addresses, Port, new Exception("AsyncTcpClient - Send - DisConnect"));
                return;
            }

            try
            {
                AsyncClient.BeginSend(datagram, 0, datagram.Length, SocketFlags.None, HandleDatagramWritten, AsyncClient);
            }
            catch (Exception e)
            {
                RaiseServerExceptionOccurred(Addresses, Port, e);
            }
        }

        public void Send(string datagram)
        {
            Send(this.Encoding.GetBytes(datagram));
        }


        private void HandleDatagramWritten(IAsyncResult ar)
        {
            try
            {
                ((Socket)ar.AsyncState).EndSend(ar);
            }
            catch (Exception e)
            {
                RaiseServerExceptionOccurred(Addresses, Port, e);
            }
        }
        #endregion

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue && disposing)
            {

                try
                {
                    Close();
                    if (AsyncClient != null)
                        AsyncClient = null;
                }
                catch (SocketException e)
                {
                    MonoBehaviour.print("TCPClient[Dispose]" + e);
                }

                // TODO: 释放未托管的资源(未托管的对象)并在以下内容中替代终结器。
                // TODO: 将大型字段设置为 null。

                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(bool disposing) 拥有用于释放未托管资源的代码时才替代终结器。
        // ~AsyncTcpClient() {
        //   // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
        //   Dispose(false);
        // }

        // 添加此代码以正确实现可处置模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(bool disposing) 中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了终结器，则取消注释以下行。
            GC.SuppressFinalize(this);
        }
        #endregion
    }

    /// <summary>
    /// 客户端State
    /// </summary>
    internal class TcpServerState
    {
        /// <summary>
        /// 客户端
        /// </summary>
        public Socket TcpClient { get; private set; }

        /// <summary>
        /// 客户端字节数组
        /// </summary>
        public byte[] Buffer { get; private set; }

        /// <summary>
        /// 客户端构造函数
        /// </summary>
        /// <param name="tcpclient"></param>
        /// <param name="buffer"></param>
        public TcpServerState(Socket tcpclient, int length = 1024)
        {
            if (tcpclient == null)
                MonoBehaviour.print("Socket is null");
            if (length <= 0)
                MonoBehaviour.print("buffer length <= 0");

            this.TcpClient = tcpclient;
            this.Buffer = new byte[length];
        }


    }

    /// <summary>
    /// 与服务器的连接已建立事件
    /// </summary>
    public class TcpServerConnectedEventArgs : EventArgs
    {
        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }

        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; private set; }


        /// <summary>
        /// 与服务器的连接已建立事件参数
        /// </summary>
        /// <param name="iPAddresses">服务器IP地址表</param>
        /// <param name="port">服务器端口</param>
        public TcpServerConnectedEventArgs(IPAddress[] iPAddresses, int port)
        {
            if (iPAddresses == null)
                MonoBehaviour.print("iPAddresses is null");

            this.Addresses = iPAddresses;
            this.Port = port;
        }

        public override string ToString()
        {
            string str = string.Empty;
            for (int i = 0; i < Addresses.Length; i++)
                str = str + Addresses[i].ToString() + ',';

            str = str.TrimEnd(',');
            str = str + ":" + Port.ToString(CultureInfo.InvariantCulture);
            return str;
        }
    }

    /// <summary>
    /// 与服务器的连接断开事件参数
    /// </summary>
    public class TcpServerDisconnectedEventArgs : EventArgs
    {
        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }
        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// 与服务器连接断开事件参数
        /// </summary>
        /// <param name="iPAddresses">服务器IP地址列表</param>
        /// <param name="Port">服务器端口</param>
        public TcpServerDisconnectedEventArgs(IPAddress[] iPAddresses, int Port)
        {
            if (iPAddresses == null)
                Console.WriteLine("iPAddresses is null");

            this.Addresses = iPAddresses;
            this.Port = Port;
        }

        public override string ToString()
        {
            string str = string.Empty;
            for (int i = 0; i < Addresses.Length; i++)
                str = str + Addresses[i].ToString() + ',';

            str = str.TrimEnd(',');
            str = str + ":" + Port.ToString(CultureInfo.InvariantCulture);
            return str;
        }
    }

    /// <summary>
    /// 接收到数据报文事件参数
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class TcpDatagramReceivedEventArgs<T> : EventArgs
    {
        /// <summary>
        /// 客户端
        /// </summary>
        public Socket TcpClient { get; private set; }

        /// <summary>
        /// 报文
        /// </summary>
        public T Datagram { get; private set; }

        /// <summary>
        /// 接收到数据报文事件参数
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        /// <param name="datagram">报文</param>
        public TcpDatagramReceivedEventArgs(Socket tcpClient, T datagram)
        {
            this.TcpClient = tcpClient;
            this.Datagram = datagram;
        }
    }

    /// <summary>
    /// 与服务器的连接发生异常事件参数
    /// </summary>
    public class TcpServerExceptionOccurredEventArgs : EventArgs
    {
        /// <summary>
        /// 服务器IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }

        /// <summary>
        /// 服务器端口
        /// </summary>
        public int Port { get; private set; }

        /// <summary>
        /// 内部异常
        /// </summary>
        public Exception Exception { get; private set; }

        public TcpServerExceptionOccurredEventArgs(IPAddress[] iPAddresses, int port, Exception exception)
        {
            if (iPAddresses == null)
                Console.WriteLine("iPAddresses is null");

            this.Addresses = iPAddresses;
            this.Port = port;
            this.Exception = exception;
        }

        public override string ToString()
        {
            string str = string.Empty;
            for (int i = 0; i < Addresses.Length; i++)
                str = str + Addresses[i].ToString() + ',';

            str = str.TrimEnd(',');
            str = str + ":" + Port.ToString(CultureInfo.InvariantCulture);
            return str;
        }
    }
}
